package com.ipan.jfinal.simpleApi;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ipan.jfinal.controller.ResultDefineManager;
import com.ipan.kits.text.StrFormatter;
import com.ipan.kits.time.DateFormatUtil;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.kit.Kv;

/**
 * 调用次数、访问权限限制处理
 * 
 * @author iPan
 * @date 2023-05-31
 */
public class SimpleApiCounterInterceptor implements Interceptor {
	
	/**
	 * 调用计数信息
	 */
	public static class CallInfo {
		private AtomicInteger count;
		private Date time;
		public AtomicInteger getCount() {
			return count;
		}
		public void setCount(AtomicInteger count) {
			this.count = count;
		}
		public Date getTime() {
			return time;
		}
		public void setTime(Date time) {
			this.time = time;
		}
	}
	
	private Logger log = LoggerFactory.getLogger(getClass());
	private static final Object LOCK = new Object();
	
	public SimpleApiCounterInterceptor() {
	}
	
	@Override
	public void intercept(Invocation inv) {
		SimpleApiController con = (SimpleApiController) inv.getController(); // 必须依赖SimpleApiController
		String userId = con.getAttr("userId"); // 用户ID
		String orgCusSendID = con.getAttr("orgCusSendID"); // 流水号
		// 调用方法
		Method method = inv.getMethod(); 
		// 调用次数、权限校验
		SimpleApi simpleApi = method.getAnnotation(SimpleApi.class);
		// 访问权限单独校验，不管次数是否需要校验；
		if (simpleApi != null) {
			String roleCode = simpleApi.role();
			if (!validateApiRole(userId, roleCode)) {
				errorRequest(con, userId, orgCusSendID, ResultDefineManager.me().getFail().getLeft(), StrFormatter.format("接口[{}]访问权限不足，请联系管理员！", simpleApi.name()));
				return ;
			}
		}
		
		// 是否需要限制次数
		if (!con.getCallLimitModel() || simpleApi == null) { // 不需要限制次数
			// 继续执行
			inv.invoke();
			return ;
		}
		
		int maxCount = simpleApi.maxCount(); // 最大调用次数
		String methodRemark = simpleApi.name(); // 接口描述
		String methodName = inv.getMethodName();
		String methodKey = con.getClass().getCanonicalName() + "." + methodName; // 映射KEY
		// 当前用户的映射表
		Map<String, CallInfo> mappingTable = SimpleApiManager.getMethodMapping().get(userId);
		if (mappingTable == null) { // 首次调用
			synchronized (LOCK) {
				if (mappingTable == null) {
					mappingTable = new HashMap<>();
					SimpleApiManager.getMethodMapping().put(userId, mappingTable);
				}
			}
		}
		// 对应方法调用的数量
		CallInfo callInfo = mappingTable.get(methodKey);
		if (callInfo == null) { // 首次调用
			synchronized (LOCK) {
				if (callInfo == null) {
					callInfo = new CallInfo();
					callInfo.setCount(new AtomicInteger());
					callInfo.setTime(new Date());
					mappingTable.put(methodKey, callInfo);
				}
			}
		}
		
		// 当前调用次数
		AtomicInteger curCount = callInfo.getCount();
		// 计数器重置
		resetCount(callInfo);
		
		// 超过最大限制
		if (curCount.intValue() >= maxCount) {
			errorRequest(con, userId, orgCusSendID, ResultDefineManager.me().getFail().getLeft(), 
					StrFormatter.format("{}调用次数超限，每日最大次数{}。", methodRemark, maxCount));
			return ;
		}

		// 提醒客户端最大调用次数以及当前调用次数
		con.setCalls(curCount.intValue());
		con.setMaxCalls(maxCount);
		
		// 继续执行
		inv.invoke();
		
		// 调用成功加一
		boolean retState = con.getRetState();
		if (retState) {
			curCount.getAndIncrement(); // 调用次数加一
			callInfo.setTime(new Date());
		}
	}
	
	/**
	 * 返回错误提示
	 */
	public void errorRequest(SimpleApiController con, String userId, String orgCusSendID, String code, String msg) {
		Kv param = Kv.by("code", code).set("msg", msg);
		con.renderEncryptJson(param);
		log.error("{}，host={}，port={}，userId={}，orgCusSendID={}。"
				, msg, con.getRequest().getRemoteHost(), con.getRequest().getRemotePort(), userId, orgCusSendID);
	}
	
	/**
	 * 重置计数器规则
	 * 默认按天清零
	 */
	public void resetCount(CallInfo callInfo) {
		AtomicInteger count = callInfo.getCount();
		Date time = callInfo.getTime();
		Date sysDate = new Date();
		String curYmd = DateFormatUtil.formatDate("yyyyMMdd", sysDate);
		String timeYmd = DateFormatUtil.formatDate("yyyyMMdd", time);
		if (!curYmd.equals(timeYmd)) {
			count.set(0);
			callInfo.setTime(sysDate);
		}
	}
	
	/**
	 * 校验当前客户端是否有权限访问接口（具体业务请重载）
	 * @param userid USERID
	 * @param apiRole 接口标识
	 * @return 校验结果（true 成功 false 失败）
	 */
	public boolean validateApiRole(String userid, String apiRole) {
		return true;
	}

}
